#ifndef SIMPLE_SUPPORT_TUPLE_UTILS_OPERATORS_HPP
#define SIMPLE_SUPPORT_TUPLE_UTILS_OPERATORS_HPP

#include <type_traits>
#include <functional>
#include <limits>
#include <cstdint>
#include <cmath>

#include "../enum_flags_operators.hpp"
#include "../meta/list.hpp"
#include "../meta/transform.hpp"
#include "../meta/bind.hpp"
#include "../meta/find.hpp"
#include "../meta/misc.hpp"
#include "../function_utils.hpp"
#include "../int_literals.hpp"
#include "transform.hpp"

namespace simple::support
{
	using std::size_t;

	// these should be common with array_operators.hpp
	enum class operator_flag : std::uint64_t
	{
		none = 0,
		add_eq = 		1_u64 << 0,
		add =			1_u64 << 1,
		sub_eq =		1_u64 << 2,
		sub =			1_u64 << 3,
		mul_eq =		1_u64 << 4,
		mul =			1_u64 << 5,
		div_eq =		1_u64 << 6,
		div =			1_u64 << 7,
		mod_eq =		1_u64 << 8,
		mod =			1_u64 << 9,
		bit_and_eq =	1_u64 << 10,
		bit_and =		1_u64 << 11,
		bit_or_eq =		1_u64 << 12,
		bit_or =		1_u64 << 13,
		bit_xor_eq =	1_u64 << 14,
		bit_xor =		1_u64 << 15,
		bit_not =		1_u64 << 16,
		negate =		1_u64 << 17,
		affirm =		1_u64 << 18,
		lshift_eq =		1_u64 << 19,
		lshift =		1_u64 << 20,
		rshift_eq =		1_u64 << 21,
		rshift =		1_u64 << 22,
		pre_inc =		1_u64 << 23,
		post_inc =		1_u64 << 24,
		pre_dec =		1_u64 << 25,
		post_dec =		1_u64 << 26,
		deref =			1_u64 << 27,
		less =			1_u64 << 28,
		greater =		1_u64 << 29,
		less_eq =		1_u64 << 30,
		greater_eq =	1_u64 << 31,
		equal =			1_u64 << 32,
		unequal =		1_u64 << 33,
		all = 			std::numeric_limits<std::underlying_type_t<operator_flag>>::max(),
		bitwise = 		bit_and | bit_and_eq | bit_or | bit_or_eq | bit_xor | bit_xor_eq | bit_not,
		arithmetic = 	add | mul | sub | div | mod | add_eq | mul_eq | sub_eq | div_eq | mod_eq | negate | affirm,
		post =		 	post_inc | post_dec,
		pre =		 	pre_inc | pre_dec,
		iterator = 		post | pre | deref,
		relational = 	less | greater | less_eq | greater_eq | equal | unequal,
		binary =		add | mul | sub | div | mod | bit_and | bit_or | bit_xor | lshift | rshift | relational,
		unary =			bit_not | negate | affirm | deref,
		in_place =		all ^ (binary | unary | iterator)
	};

	template<> struct define_enum_flags_operators<operator_flag> : public std::true_type {};

	// TODO: got an opportunity to counter some insane fundamental integral promotion rules here
	// bool bit_op bool = int // bool is guaranteed to be 0 or 1 so logical bit op results can fit in bool, not to mention bit twiddling an int is implementation defined (simple::geom::vector does this and for a reason)
	// (unsigned short) * (unsigned short) = int // overflows for large values, which is undefined behavior for int, should promote to unsigned instead, can't guarantee no overflow, but at least not freakin UB
	// there is no reason for unary plus to promote, other than if it's used as some sort of explicit promotion operator, but I don't really like that, since proper promotion rules should be based on operators and not just types. in my eyes unary plus should be just a shorthand for copy construction
	// that said it might be better to leave these kind of descisions to the element type instead, but the defaults are just so brokeeeeen T_T the temptatioooon ToT

	struct standard_arithmetic_operator_compatibility_tag {};

	// public trait to specialize
	template <typename Type>
	struct define_tuple_operators
	{
		// a way to get to an actual tuple-like type in case Type does have the interface itself
		template <typename T, std::enable_if_t<std::is_same_v<std::remove_cvref_t<T>, Type>>* = nullptr>
		constexpr static decltype(auto) get_tuple(T&& object) noexcept
		{return std::forward_as_tuple(std::forward<T>(object));}

		// operators to enable, only considering operands that define this trait
		constexpr static operator_flag enabled_operators = operator_flag::none;
		// binary and in_place operators to enable, considering element types on the right side
		constexpr static operator_flag enabled_right_element_operators = operator_flag::none;
		// binary and in_place operators to enable, considering element types on the left side
		constexpr static operator_flag enabled_left_element_operators = operator_flag::none;

		// this template is used to change the type of the tuple
		// in context of deducing the result of operations,
		// if your tuple-like type is templated on the element types you can wire it through here
		// to support type promotion, expression templates or other special techniques (like boost safe_numerics)
		// Deduced - list of deduced result types
		// operator_flag - the operator in question
		// One - the list of element types
		// Other - the list element types of the other operand (for unary ops it's same as One)
		// Element - indicates whether this is an right/left element operation
		template <typename Deduced, operator_flag, typename One, typename Other, bool Element = false>
		using result = Type;

		// if this type is the same between two types they will be considered as operands for binary and in-place operators,
		// as long as corresponding element types support the operator,
		// this is the another thing you need in order to support type promotion, expression templates etc.
		// you can set this to the shape of the tuple (which would be size if there is no nesting),
		// along with a tag that identifies the template, if you want that extra safety
		using compatibility_tag = std::conditional_t<std::is_arithmetic_v<Type>, standard_arithmetic_operator_compatibility_tag, Type>;
	};

	// public trait to use
	// some common defaults for defining tuple operators on a type
	template <typename Type>
	struct trivial_tuple_accessor
	{
		// the type provides tuple-like interface itself, no digging or wrapping necessary
		template <typename T>
		constexpr static decltype(auto) get_tuple(T&& object) noexcept { return std::forward<T>(object); }

		template <typename Result, operator_flag, typename Other = Type, bool Element = false>
		using result = Type; // the result of binary operators is the same exact type, in all cases
		// note that, while it can help somewhat, this does not (and can not) technically prevent type promotion and associated problems,
		// to get around that properly you would need a more sophisticated solution
		// see https://www.boost.org/doc/libs/1_75_0/libs/safe_numerics/doc/html/promotion_policy.html

		using compatibility_tag = Type; // operands of binary and in-place operators must be the same exact type
	};

	template <typename T>
	using get_defined_tuple_operators = define_tuple_operators<std::remove_cvref_t<T>>;

	// public trait to specialize
	// allows you to specify an implicit conversion to be considered by binary and in-place operators,
	// since templates do not consider conversions otherwise
	template <typename From>
	struct tuple_operator_implicit_conversion { using type = std::remove_cvref_t<From>; };
	template <typename From>
	using tuple_operator_implicit_conversion_t =
		typename tuple_operator_implicit_conversion<std::remove_cvref_t<From>>::type;


	template <typename Tuple, typename Range>
	struct tuple_to_list;
	template <typename Tuple, size_t... index>
	struct tuple_to_list<Tuple, std::index_sequence<index...>>
	{
		using type = meta::list<std::tuple_element_t<index,Tuple>...>;
	};
	template <typename Tuple>
	using tuple_to_list_t = typename tuple_to_list<std::remove_cvref_t<Tuple>, std::make_index_sequence<std::tuple_size_v<std::remove_cvref_t<Tuple>>>>::type;

	template <typename... Ts>
	constexpr bool all_of_v(meta::list<Ts...>) { return (Ts::value && ...); }

	template <typename... Ts>
	constexpr bool any_of_v(meta::list<Ts...>) { return (Ts::value || ...); }

	template <template <typename...> class Op, typename L1, typename L2, typename Enabled = void>
	struct meta_zip {};
	template <template <typename...> class Op, typename... E1, typename... E2>
	struct meta_zip<Op,meta::list<E1...>, meta::list<E2...>, std::enable_if_t<sizeof...(E1) == sizeof...(E2)>>
	{
		using type = meta::list<Op<E1,E2>...>;
	};
	template <template <typename...> class Op, typename L1, typename L2>
	using meta_zip_t = typename meta_zip<Op, L1, L2>::type;

	// public trait to specialize
	// TODO: parameterize on operator_flag
	template <typename T1, typename T2>
	struct is_operator_compatible : std::is_same<
		typename get_defined_tuple_operators<T1>::compatibility_tag, typename get_defined_tuple_operators<T2>::compatibility_tag> {};
	template <typename T1, typename T2>
	constexpr bool is_operator_compatible_v = is_operator_compatible<T1,T2>::value;

	// TODO: parameterize on operator_flag
	template <typename T, typename List>
	constexpr bool all_operator_compatible_v = meta::find_t<
		meta::bind<meta::not_f<is_operator_compatible>::template function, T>,
		List
	>::value == meta::list_size_v<List>;

namespace operators
{
	struct adl {};

// well, ended up with  macros after all... and still lots of duplication
// FIXME: either get rid of macros or make better use of them

#define SIMPLE_SUPPORT_DEFINE_UNARY_OPERATOR(op_symbol, op_flag) \
constexpr auto detail_##op_flag##_fun = [](auto&& x) -> decltype(auto) { return op_symbol std::forward<decltype(x)>(x); }; \
using detail_##op_flag##_fun_t = decltype(detail_##op_flag##_fun); \
template <typename Tuple, \
	typename OperatorDef = get_defined_tuple_operators<Tuple>, \
	typename Elements = tuple_to_list_t<decltype(OperatorDef::get_tuple(std::declval<Tuple>()))>,\
	std::enable_if_t<all_of_v(meta::transform_t<Elements, \
		meta::bind<std::is_invocable,detail_##op_flag##_fun_t>::template bound>{} \
	)> * = nullptr,\
	typename ResultElements = meta::transform_t<Elements, \
		meta::bind_t<std::invoke_result,detail_##op_flag##_fun_t>::template bound>,\
	std::enable_if_t<OperatorDef::enabled_operators && operator_flag::op_flag>* = nullptr> \
[[nodiscard]] constexpr auto operator op_symbol (Tuple&& one) \
	-> typename OperatorDef::template result<ResultElements, operator_flag::op_flag, Elements, false> \
{ \
	return transform(detail_##op_flag##_fun, \
		OperatorDef::get_tuple(std::forward<Tuple>(one))); \
}

SIMPLE_SUPPORT_DEFINE_UNARY_OPERATOR(~, bit_not)
SIMPLE_SUPPORT_DEFINE_UNARY_OPERATOR(-, negate)
SIMPLE_SUPPORT_DEFINE_UNARY_OPERATOR(+, affirm)
SIMPLE_SUPPORT_DEFINE_UNARY_OPERATOR(*, deref)

#undef SIMPLE_SUPPORT_DEFINE_UNARY_OPERATOR

#define SIMPLE_SUPPORT_DEFINE_POST_OPERATOR(op_symbol, op_flag) \
constexpr auto detail_##op_flag##_fun = [](auto& x){ return x op_symbol; }; \
using detail_##op_flag##_fun_t = decltype(detail_##op_flag##_fun); \
template <typename Tuple, \
	typename OperatorDef = get_defined_tuple_operators<Tuple>, \
	typename Elements = tuple_to_list_t<decltype(OperatorDef::get_tuple(std::declval<Tuple>()))>,\
	typename ElementRefs = meta::transform_t<Elements,std::add_lvalue_reference_t>,\
	std::enable_if_t<OperatorDef::enabled_operators && operator_flag::op_flag>* = nullptr, \
	std::enable_if_t<all_of_v(meta::transform_t<ElementRefs, \
		meta::bind<std::is_invocable,detail_##op_flag##_fun_t>::template bound>{} \
	)> * = nullptr,\
	typename ResultElements = meta::transform_t<ElementRefs, \
		meta::bind_t<std::invoke_result,detail_##op_flag##_fun_t>::template bound>\
> \
[[nodiscard]] constexpr auto operator op_symbol (Tuple&& one, int) \
	-> typename OperatorDef::template result<ResultElements, operator_flag::op_flag, Elements, false> \
{ \
	return transform(detail_##op_flag##_fun, \
		OperatorDef::get_tuple(std::forward<Tuple>(one))); \
}

SIMPLE_SUPPORT_DEFINE_POST_OPERATOR(++, post_inc)
SIMPLE_SUPPORT_DEFINE_POST_OPERATOR(--, post_dec)

#undef SIMPLE_SUPPORT_DEFINE_POST_OPERATOR

#define SIMPLE_SUPPORT_DEFINE_PRE_OPERATOR(op_symbol, op_flag) \
constexpr auto detail_##op_flag##_fun = [](auto& x) -> decltype(op_symbol x) \
	{ return op_symbol x; }; \
using detail_##op_flag##_fun_t = decltype(detail_##op_flag##_fun); \
template <typename Tuple, \
	typename OperatorDef = get_defined_tuple_operators<Tuple>, \
	std::enable_if_t<OperatorDef::enabled_operators && operator_flag::op_flag>* = nullptr, \
	typename Elements = tuple_to_list_t<decltype(OperatorDef::get_tuple(std::declval<Tuple>()))>,\
	typename ElementRefs = meta::transform_t<Elements,std::add_lvalue_reference_t>,\
	std::enable_if_t<all_of_v(meta::transform_t<ElementRefs, \
		meta::bind<std::is_invocable,detail_##op_flag##_fun_t>::template bound>{} \
	)> * = nullptr\
> \
constexpr Tuple& operator op_symbol (Tuple& one) \
{ \
	transform(detail_##op_flag##_fun, \
		OperatorDef::get_tuple(one)); \
	return one; \
}

SIMPLE_SUPPORT_DEFINE_PRE_OPERATOR(++, pre_inc)
SIMPLE_SUPPORT_DEFINE_PRE_OPERATOR(--, pre_dec)

#undef SIMPLE_SUPPORT_DEFINE_PRE_OPERATOR

#define SIMPLE_SUPPORT_DEFINE_BINARY_OPERATOR(op_symbol, op_flag) \
struct detail_##op_flag##_fun_t \
{ \
	template <typename X, typename Y> \
	constexpr auto operator()(X&& x, Y&& y) const \
		-> decltype(std::forward<X>(x) op_symbol std::forward<Y>(y)) \
	{ \
		return std::forward<X>(x) op_symbol std::forward<Y>(y); \
	} \
\
	template <typename X, typename Y, operator_flag of = operator_flag::op_flag, \
		std::enable_if_t<std::is_floating_point_v<std::remove_cvref_t<X>> && \
			std::is_floating_point_v<std::remove_cvref_t<Y>> && \
			operator_flag::mod == operator_flag::op_flag \
		>* = nullptr \
	> \
	constexpr auto operator()(X&& x, Y&& y) const \
	{ \
		return std::fmod(std::forward<X>(x), std::forward<Y>(y)); \
	} \
}; \
constexpr detail_##op_flag##_fun_t detail_##op_flag##_fun{}; \
\
template <typename Tuple, typename Other,  void* = nullptr, \
	typename OperatorDef = get_defined_tuple_operators<Tuple>, \
	typename OtherOperatorDef = get_defined_tuple_operators<Other>, \
	typename Elements = tuple_to_list_t<decltype(OperatorDef::get_tuple(std::declval<Tuple>()))>,\
	typename OtherElements = tuple_to_list_t<decltype(OtherOperatorDef::get_tuple(std::declval<Other>()))>,\
	std::enable_if_t<OperatorDef::enabled_operators && operator_flag::op_flag>* = nullptr, \
	std::enable_if_t<OtherOperatorDef::enabled_operators && operator_flag::op_flag>* = nullptr, \
	std::enable_if_t<is_operator_compatible_v<Tuple, Other>>* = nullptr, \
	std::enable_if_t<all_of_v(meta_zip_t< \
		meta::bind<std::is_invocable,detail_##op_flag##_fun_t>::template bound, \
		Elements, OtherElements \
	>{})>* = nullptr, \
	typename ResultElements = meta_zip_t< \
		meta::bind_t<std::invoke_result,detail_##op_flag##_fun_t>::template bound, \
		Elements, OtherElements \
	>\
> \
[[nodiscard]] constexpr auto operator op_symbol (Tuple&& one, Other&& other) \
	-> typename OperatorDef::template result<ResultElements, operator_flag::op_flag, OtherElements, false> \
{ \
	return transform(detail_##op_flag##_fun, \
		OperatorDef::get_tuple(std::forward<Tuple>(one)), \
		OtherOperatorDef::get_tuple(std::forward<Other>(other))); \
} \
\
template <typename T1, typename T2, \
	typename C1 = tuple_operator_implicit_conversion_t<T1>, \
	typename C2 = tuple_operator_implicit_conversion_t<T2>, \
	typename OpDef1 = get_defined_tuple_operators<C1>, \
	typename OpDef2 = get_defined_tuple_operators<C2>, \
	std::enable_if_t< \
		OpDef1::enabled_operators && operator_flag::op_flag || \
		OpDef2::enabled_operators && operator_flag::op_flag \
	>* = nullptr, \
	std::enable_if_t<std::is_invocable_v<decltype(simple::support::operators::operator op_symbol<C1,C2,nullptr>),T1,T2>>* = nullptr,\
	std::enable_if_t< \
		not std::is_same_v<std::remove_cvref_t<T1>,C1> || \
		not std::is_same_v<std::remove_cvref_t<T2>,C2> \
	>* = nullptr \
> \
[[nodiscard]] constexpr decltype(auto) operator op_symbol (T1&& one, T2&& other) \
{ \
	return simple::support::operators::operator op_symbol<C1,C2,nullptr>(std::forward<T1>(one), std::forward<T2>(other)); \
} \
\
template <typename Tuple, typename Other, \
	typename OperatorDef = get_defined_tuple_operators<Tuple>, \
	typename OtherOperatorDef = get_defined_tuple_operators<Other>, \
	typename Elements = tuple_to_list_t<decltype(OperatorDef::get_tuple(std::declval<Tuple>()))>,\
	typename OtherElements = tuple_to_list_t<decltype(OtherOperatorDef::get_tuple(std::declval<Other>()))>,\
	bool ops_defined = OperatorDef::enabled_right_element_operators && operator_flag::op_flag, \
	std::enable_if_t<ops_defined>* = nullptr,\
	std::enable_if_t<not is_operator_compatible_v<Tuple,Other>>* = nullptr,\
	std::enable_if_t<all_of_v(meta::transform_t< \
		Elements, \
		meta::bind_after< \
			meta::bind<std::is_invocable, \
				detail_##op_flag##_fun_t>::template bound, \
		Other>::template bound \
	>{}) >* = nullptr,\
	typename Result = typename OperatorDef::template result<std::conditional_t<ops_defined, \
		meta::transform_t< \
			Elements, \
			meta::bind_after_t< \
				meta::bind<std::invoke_result, \
					detail_##op_flag##_fun_t>::template bound, \
			Other>::template bound \
		>, \
		Elements>, operator_flag::op_flag, Other, true>, \
	std::enable_if_t< \
		not all_operator_compatible_v<Tuple, OtherElements> \
		&& (all_operator_compatible_v<Other, Elements> \
			|| is_operator_compatible_v<tuple_operator_implicit_conversion_t<Result>, Tuple>) \
	>* = nullptr \
> \
[[nodiscard]] constexpr Result operator op_symbol \
( \
	Tuple && one, \
	Other && other \
 ) \
{ \
	return transform([&other](auto&& x) { \
		return detail_##op_flag##_fun(std::forward<decltype(x)>(x), other); }, \
		OperatorDef::get_tuple(std::forward<Tuple>(one))); \
} \
\
template <typename Tuple, typename Other, \
	typename OperatorDef = get_defined_tuple_operators<Tuple>, \
	typename OtherOperatorDef = get_defined_tuple_operators<Other>, \
	typename Elements = tuple_to_list_t<decltype(OperatorDef::get_tuple(std::declval<Tuple>()))>,\
	typename OtherElements = tuple_to_list_t<decltype(OtherOperatorDef::get_tuple(std::declval<Other>()))>,\
	bool ops_defined = OperatorDef::enabled_left_element_operators && operator_flag::op_flag, \
	std::enable_if_t<ops_defined>* = nullptr,\
	std::enable_if_t<not is_operator_compatible_v<Tuple,Other>>* = nullptr,\
	std::enable_if_t<all_of_v(meta::transform_t< \
		Elements, \
		meta::bind<std::is_invocable, \
			detail_##op_flag##_fun_t, Other>::template bound \
	>{}) >* = nullptr,\
	typename Result = typename OperatorDef::template result<std::conditional_t<ops_defined, \
		meta::transform_t< \
			Elements, \
			meta::bind_t<std::invoke_result, \
				detail_##op_flag##_fun_t, Other>::template bound \
		>, \
		Elements>, operator_flag::op_flag, Other, true>, \
	std::enable_if_t< \
		not all_operator_compatible_v<Tuple, OtherElements> \
		&& (all_operator_compatible_v<Other, Elements> \
			|| is_operator_compatible_v<tuple_operator_implicit_conversion_t<Result>, Tuple>) \
	>* = nullptr \
> \
[[nodiscard]] constexpr Result operator op_symbol \
( \
	Other && one, \
	Tuple && other \
) \
{ \
	return transform([&one](auto&& x) { \
		return detail_##op_flag##_fun(one, std::forward<decltype(x)>(x)); }, \
		OperatorDef::get_tuple(std::forward<Tuple>(other))); \
}

SIMPLE_SUPPORT_DEFINE_BINARY_OPERATOR(+, add)
SIMPLE_SUPPORT_DEFINE_BINARY_OPERATOR(-, sub)
SIMPLE_SUPPORT_DEFINE_BINARY_OPERATOR(*, mul)
SIMPLE_SUPPORT_DEFINE_BINARY_OPERATOR(/, div)
SIMPLE_SUPPORT_DEFINE_BINARY_OPERATOR(%, mod)
SIMPLE_SUPPORT_DEFINE_BINARY_OPERATOR(&, bit_and)
SIMPLE_SUPPORT_DEFINE_BINARY_OPERATOR(|, bit_or)
SIMPLE_SUPPORT_DEFINE_BINARY_OPERATOR(^, bit_xor)
SIMPLE_SUPPORT_DEFINE_BINARY_OPERATOR(<<, lshift)
SIMPLE_SUPPORT_DEFINE_BINARY_OPERATOR(>>, rshift)

SIMPLE_SUPPORT_DEFINE_BINARY_OPERATOR(<, less)
SIMPLE_SUPPORT_DEFINE_BINARY_OPERATOR(>, greater)
SIMPLE_SUPPORT_DEFINE_BINARY_OPERATOR(<=, less_eq)
SIMPLE_SUPPORT_DEFINE_BINARY_OPERATOR(>=, greater_eq)
SIMPLE_SUPPORT_DEFINE_BINARY_OPERATOR(==, equal)
SIMPLE_SUPPORT_DEFINE_BINARY_OPERATOR(!=, unequal)

#undef SIMPLE_SUPPORT_DEFINE_BINARY_OPERATOR

#define SIMPLE_SUPPORT_DEFINE_IN_PLACE_OPERATOR(op_symbol, op_flag) \
constexpr auto detail_##op_flag##_fun = overload { \
	[](auto& x, auto&& y) -> decltype(x op_symbol std::forward<decltype(y)>(y)) \
	{ \
		return x op_symbol std::forward<decltype(y)>(y); \
	},\
	[](auto& x, auto&& y) -> std::enable_if_t< \
		std::is_floating_point_v<std::remove_cvref_t<decltype(x)>> && \
		std::is_floating_point_v<std::remove_cvref_t<decltype(y)>> && \
		operator_flag::mod_eq == operator_flag::op_flag \
	> \
	{ \
		x = std::fmod(x, std::forward<decltype(y)>(y)); \
	}\
}; \
using detail_##op_flag##_fun_t = decltype(detail_##op_flag##_fun); \
\
template <typename Tuple, typename Other, void* = nullptr, \
	typename OperatorDef = get_defined_tuple_operators<Tuple>, \
	typename OtherOperatorDef = get_defined_tuple_operators<Other>, \
	typename Elements = tuple_to_list_t<decltype(OperatorDef::get_tuple(std::declval<Tuple>()))>,\
	typename OtherElements = tuple_to_list_t<decltype(OtherOperatorDef::get_tuple(std::declval<Other>()))>,\
	typename ElementRefs = meta::transform_t<Elements,std::add_lvalue_reference_t>,\
	std::enable_if_t<OperatorDef::enabled_operators && operator_flag::op_flag>* = nullptr, \
	std::enable_if_t<OtherOperatorDef::enabled_operators && operator_flag::op_flag>* = nullptr, \
	std::enable_if_t<is_operator_compatible_v<Tuple, Other>>* = nullptr, \
	std::enable_if_t<all_of_v(meta_zip_t< \
		meta::bind<std::is_invocable,detail_##op_flag##_fun_t>::template bound, \
		ElementRefs, OtherElements \
	>{})>* = nullptr> \
constexpr Tuple& operator op_symbol \
( \
	Tuple& one, \
	Other&& other \
) \
{ \
	transform(detail_##op_flag##_fun, OperatorDef::get_tuple(one), \
		OtherOperatorDef::get_tuple(std::forward<Other>(other))); \
	return one; \
} \
\
template <typename Tuple, typename Other, \
	typename OperatorDef = get_defined_tuple_operators<Tuple>, \
	std::enable_if_t<OperatorDef::enabled_operators && operator_flag::op_flag>* = nullptr, \
	typename OtherConv = tuple_operator_implicit_conversion_t<Other>, \
	typename OtherOperatorDef = get_defined_tuple_operators<OtherConv>, \
	std::enable_if_t<OtherOperatorDef::enabled_operators && operator_flag::op_flag>* = nullptr, \
	std::enable_if_t< \
		(not std::is_same_v<OtherConv,std::remove_cvref_t<Other>>) \
	>* = nullptr \
> \
constexpr decltype(auto) operator op_symbol (Tuple& one, Other&& other) \
{ \
	return operator op_symbol<Tuple,OtherConv,nullptr>(one, std::forward<Other>(other)); \
} \
\
template <typename Tuple, typename Other, \
	typename OperatorDef = get_defined_tuple_operators<Tuple>, \
	typename Elements = tuple_to_list_t<decltype(OperatorDef::get_tuple(std::declval<Tuple>()))>,\
	typename ElementRefs = meta::transform_t<Elements,std::add_lvalue_reference_t>,\
	std::enable_if_t<(OperatorDef::enabled_right_element_operators && operator_flag::op_flag)>* = nullptr, \
	std::enable_if_t<not is_operator_compatible_v<Tuple, Other>>* = nullptr, \
	std::enable_if_t< \
		all_of_v(meta::transform_t< \
			ElementRefs, \
			meta::bind_after< \
				meta::bind<std::is_invocable, \
					detail_##op_flag##_fun_t>::template bound, \
			Other>::template bound \
		>{}) \
	>* = nullptr \
> \
constexpr Tuple& operator op_symbol \
( \
	Tuple & one, \
	Other && other \
) \
{ \
	transform([&other](auto& x) { \
		detail_##op_flag##_fun(x, other); }, \
		OperatorDef::get_tuple(one)); \
	return one; \
} \
template <typename Tuple, typename Other, \
	typename OperatorDef = get_defined_tuple_operators<Tuple>, \
	typename Elements = tuple_to_list_t<decltype(OperatorDef::get_tuple(std::declval<Tuple>()))>,\
	std::enable_if_t<(OperatorDef::enabled_left_element_operators && operator_flag::op_flag)>* = nullptr, \
	std::enable_if_t<not is_operator_compatible_v<Tuple, Other>>* = nullptr, \
	std::enable_if_t< \
		all_of_v(meta::transform_t< \
			Elements, \
			meta::bind<std::is_invocable, \
				detail_##op_flag##_fun_t, Other&>::template bound \
		>{}) \
	>* = nullptr \
> \
constexpr Other& operator op_symbol \
( \
	Other & one, \
	Tuple && other \
) \
{ \
	transform([&one](auto&& x) { \
		detail_##op_flag##_fun(one, std::forward<decltype(x)>(x)); }, \
		OperatorDef::get_tuple(std::forward<Tuple>(other))); \
	return one; \
}

SIMPLE_SUPPORT_DEFINE_IN_PLACE_OPERATOR(+=, add_eq)
SIMPLE_SUPPORT_DEFINE_IN_PLACE_OPERATOR(-=, sub_eq)
SIMPLE_SUPPORT_DEFINE_IN_PLACE_OPERATOR(*=, mul_eq)
SIMPLE_SUPPORT_DEFINE_IN_PLACE_OPERATOR(/=, div_eq)
SIMPLE_SUPPORT_DEFINE_IN_PLACE_OPERATOR(%=, mod_eq)
SIMPLE_SUPPORT_DEFINE_IN_PLACE_OPERATOR(&=, bit_and_eq)
SIMPLE_SUPPORT_DEFINE_IN_PLACE_OPERATOR(|=, bit_or_eq)
SIMPLE_SUPPORT_DEFINE_IN_PLACE_OPERATOR(^=, bit_xor_eq)
SIMPLE_SUPPORT_DEFINE_IN_PLACE_OPERATOR(<<=, lshift_eq)
SIMPLE_SUPPORT_DEFINE_IN_PLACE_OPERATOR(>>=, rshift_eq)

#undef SIMPLE_SUPPORT_DEFINE_IN_PLACE_OPERATOR

} // namespace operators

} // namespace simple::support

#endif /* end of include guard */
